package com.socialcoco.serviceImpl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.socialcoco.entity.Grade;
import com.socialcoco.entity.Seckillproduct;
import com.socialcoco.entity.mySeckill;
import com.socialcoco.mapper.SeckillproductMapper;
import com.socialcoco.service.MyseckillService;
import com.socialcoco.service.SeckillproductService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.socialcoco.util.timeGet;
import lombok.extern.slf4j.Slf4j;
import org.redisson.RedissonSemaphore;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 赵志浩
 * @since 2022-05-30 07:11:11
 */
@Service
@Slf4j
public class SeckillproductServiceImpl extends ServiceImpl<SeckillproductMapper, Seckillproduct> implements SeckillproductService {


    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    MyseckillServiceImpl myseckillService;
    @Autowired
    GradeServiceImpl gradeService;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    private final String SECKILL_CHARE_PREFIX = "seckill:skus:";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";
    private final String SKU_USER_SEMAPHORE = "seckill:user:";
    @Override
    public List<Seckillproduct> list() {
        if(redisTemplate.hasKey(SECKILL_CHARE_PREFIX)){
            Set<Object> keys = redisTemplate.opsForHash().keys(SECKILL_CHARE_PREFIX);
            //从redis中获取
            List<Object> list1=new ArrayList<>();
            for (Object key : keys) {
                list1.add(key);
            }
            List<Object> list = redisTemplate.opsForHash().multiGet(SECKILL_CHARE_PREFIX,list1);

            List<Seckillproduct> collect = list.stream().map(item -> {
                String items = (String) item;
                Seckillproduct redisTo = JSON.parseObject(items, Seckillproduct.class);
                // redisTo.setRandomCode(null);当前秒杀开始需要随机码
                return redisTo;
            }).collect(Collectors.toList());
            return collect;
        }
        else {
            Long ttl = timeGet.getSecondsTobeforedawn();
            String token = UUID.randomUUID().toString().replace("-", "");
            List<Seckillproduct> list = super.list();
            list.stream().forEach(item->{
                String seckillValue = JSON.toJSONString(item);
                redisTemplate.opsForHash().put(SECKILL_CHARE_PREFIX,String.valueOf(item.getId()),seckillValue);
                redisTemplate.expire(SECKILL_CHARE_PREFIX,ttl,TimeUnit.SECONDS);
                RSemaphore rSemaphore=redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE+item.getId());
                rSemaphore.trySetPermits(item.getNum());
                rSemaphore.expire(ttl,TimeUnit.SECONDS);
            });
            return list;
        }
    }

    /**
     * 点击秒杀兑换
     * 由于没有把用户的信息添加到thredlocal中，所以通过前端传过来
     * @return
     */
    @Override
    @SentinelResource(value = "getSkusResource",blockHandler = "blockHandler")
    @Transactional(rollbackFor =RuntimeException.class )
    public String killProduct(String uid,String key) throws InterruptedException, ExecutionException {
        long s2 = System.currentTimeMillis();
        //获取redis中的商品数据
        String o= (String) redisTemplate.opsForHash().get(SECKILL_CHARE_PREFIX, key);
        if(o==null){
            return "frame";
        }
        Seckillproduct redisTo = JSON.parseObject(o, Seckillproduct.class);
        Grade uid1 = gradeService.getOne(new QueryWrapper<Grade>().eq("uid", uid));
        long s=System.currentTimeMillis();
        if(uid1==null || uid1.getFraction()<redisTo.getRewardprice()){
            return null;
        }
        //验证信号量是否大于0
        String s1 = redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + key);
        Integer count =Integer.valueOf(s1);
        if(count>0&&redisTo.getNum()>=count){
            //库存足够，限量正确
            String redisKey=uid+"-"+key;
            Long ttl = timeGet.getSecondsTobeforedawn();
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(SKU_USER_SEMAPHORE+redisKey, String.valueOf(1), ttl, TimeUnit.SECONDS);
            if(aBoolean){
                //为真说明今天没有兑换过,信号量减一
                RSemaphore semaphore=redissonClient.getSemaphore("seckill:stock:"+key);
                boolean b = semaphore.tryAcquire(1, 100, TimeUnit.MILLISECONDS);
                if(b){
                    //如果信号量减一成功
                    //将数据保存到数据库我的兑奖表里面
                    //更新数量
                    //减少积分
                    String o1= (String) redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE+key);
                    int nums= Integer.valueOf(o1);
                    uid1.setFraction(uid1.getFraction()-redisTo.getRewardprice());
                    CompletableFuture<Void> future1=CompletableFuture.runAsync(()->{
                        gradeService.updateById(uid1);
                    },threadPoolExecutor);
                    CompletableFuture<Void> future2=CompletableFuture.runAsync(()->{
                        mySeckill myseckill=new mySeckill();
                        myseckill.setCreateTime(new Date());
                        myseckill.setUid(Integer.valueOf(uid));
                        myseckill.setSid(Integer.valueOf(key));
                        myseckill.setOverdueTime(new Date(System.currentTimeMillis()+2678400000l));
                        myseckillService.save(myseckill);
                    },threadPoolExecutor);
                    CompletableFuture<Void> future3=CompletableFuture.runAsync(()->{
                        redisTo.setNum(nums);
                        String seckillValue = JSON.toJSONString(redisTo);
                        redisTemplate.opsForHash().put(SECKILL_CHARE_PREFIX,key,seckillValue);
                    },threadPoolExecutor);
                    CompletableFuture<Void> future4=CompletableFuture.runAsync(()->{
                        super.updateById(redisTo);
                    },threadPoolExecutor);
                    CompletableFuture.allOf(future1,future2,future3,future4).get();
                    long s3 = System.currentTimeMillis();
                    log.info("耗时..." + (s2 - s3));
                }
            }
            else {
                return "exist";
            }

        }
        return null;
    }
    public static String blockHandler(String uid,String key,BlockException e) {

        log.error("getSkusResource被限流了,{}",e.getMessage());
        return null;
    }

}
